package com.hanter.android.radui.navigation;

import android.os.Build;
import android.os.Bundle;

import androidx.annotation.IdRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.navigation.NavController;
import androidx.navigation.NavDestination;
import androidx.navigation.NavGraph;
import androidx.navigation.NavOptions;
import androidx.navigation.ui.AppBarConfiguration;

import com.hanter.android.radui.R;
import com.hanter.android.radwidget.TitleBar;
import com.hanter.android.radwidget.cupertino.CupertinoTabBar;
import com.hanter.android.radwidget.cupertino.TabBarItem;

import java.lang.ref.WeakReference;
import java.util.Set;

public class NavigationUtil {

    public static void setupTitleBarWithNavController(@NonNull TitleBar titleBar,
                                                       @NonNull NavController navController,
                                                       @NonNull AppBarConfiguration configuration) {
        navController.addOnDestinationChangedListener(
                new TitleBarOnDestinationChangedListener(titleBar, configuration));
    }

    /**
     * Sets up a {@link CupertinoTabBar} for use with a {@link NavController}. This will call
     * {@link #onNavDestinationSelected(TabBarItem, NavController)} when a menu item is selected. The
     * selected item in the BottomNavigationView will automatically be updated when the destination
     * changes.
     *
     * @param tabBar The tabBar that should be kept in sync with
     *                             changes to the NavController.
     * @param navController The NavController that supplies the primary menu.
     *                      Navigation actions on this NavController will be reflected in the
     *                      selected item in the BottomNavigationView.
     */
    @RequiresApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
    public static void setupWithNavController(
            @NonNull final CupertinoTabBar tabBar,
            @NonNull final NavController navController) {

        tabBar.setOnTabChangeListener(item -> onNavDestinationSelected(item, navController));

        final WeakReference<CupertinoTabBar> weakReference =
                new WeakReference<>(tabBar);

        navController.addOnDestinationChangedListener(
                new NavController.OnDestinationChangedListener() {
                    @Override
                    public void onDestinationChanged(@NonNull NavController controller,
                                                     @NonNull NavDestination destination, @Nullable Bundle arguments) {
                        CupertinoTabBar view = weakReference.get();
                        if (view == null) {
                            navController.removeOnDestinationChangedListener(this);
                            return;
                        }

                        for (int i = 0; i < view.getTabBarItems().size(); i++) {
                            TabBarItem item = view.getTabBarItem(i);
                            if (matchDestination(destination, item.getItemId())) {
                                view.setCurrentTab(i);
                            }
                        }

                    }
                });
    }


    public static boolean onNavDestinationSelected(@NonNull TabBarItem item,
                                                   @NonNull NavController navController) {
        NavOptions.Builder builder = new NavOptions.Builder()
                .setLaunchSingleTop(true)
                .setEnterAnim(R.anim.nav_default_enter_anim)
                .setExitAnim(R.anim.nav_default_exit_anim)
                .setPopEnterAnim(R.anim.nav_default_pop_enter_anim)
                .setPopExitAnim(R.anim.nav_default_pop_exit_anim);
        builder.setPopUpTo(findStartDestination(navController.getGraph()).getId(), false);
        NavOptions options = builder.build();
        try {
            navController.navigate(item.getItemId(), null, options);
            return true;
        } catch (IllegalArgumentException e) {
            return false;
        }
    }


    /**
     * Determines whether the given <code>destId</code> matches the NavDestination. This handles
     * both the default case (the destination's id matches the given id) and the nested case where
     * the given id is a parent/grandparent/etc of the destination.
     */
    @SuppressWarnings("WeakerAccess") /* synthetic access */
    static boolean matchDestination(@NonNull NavDestination destination,
                                    @IdRes int destId) {
        NavDestination currentDestination = destination;
        while (currentDestination.getId() != destId && currentDestination.getParent() != null) {
            currentDestination = currentDestination.getParent();
        }
        return currentDestination.getId() == destId;
    }

    /**
     * Determines whether the given <code>destinationIds</code> match the NavDestination. This
     * handles both the default case (the destination's id is in the given ids) and the nested
     * case where the given ids is a parent/grandparent/etc of the destination.
     */
    static boolean matchDestinations(@NonNull NavDestination destination,
                                     @NonNull Set<Integer> destinationIds) {
        NavDestination currentDestination = destination;
        do {
            if (destinationIds.contains(currentDestination.getId())) {
                return true;
            }
            currentDestination = currentDestination.getParent();
        } while (currentDestination != null);
        return false;
    }

    /**
     * Finds the actual start destination of the graph, handling cases where the graph's starting
     * destination is itself a NavGraph.
     */
    static NavDestination findStartDestination(@NonNull NavGraph graph) {
        NavDestination startDestination = graph;
        while (startDestination instanceof NavGraph) {
            NavGraph parent = (NavGraph) startDestination;
            startDestination = parent.findNode(parent.getStartDestination());
        }
        return startDestination;
    }

}
